# coding:utf-8
import socket
import threading
import time
import gzip
import base64
import math
import traceback
import json
import zlib


class server:
    def __init__(self, host, port, buffer_size):
        self.UDP = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.Host = host
        self.Port = port
        self.Send_Bytes_Pieces = {
            'crc32_1': {
                'ctime': '2020年11月08日21:20:07',
                'pieces': [],
                'atime': '2020年11月08日21:20:44'
            },
            'crc32_2': {
                'ctime': '2020年11月08日21:20:07',
                'pieces': [],
                'atime': '2020年11月08日21:20:44'
            },
        }
        self.Recv_Bytes_Pieces = {
            'crc32_1': {
                'ctime': '2020年11月08日21:39:34',
                'pieces': [],
                'atime': '2020年11月08日21:39:53'
            },
            'crc32_2': {
                'ctime': '2020年11月08日21:39:34',
                'pieces': [],
                'atime': '2020年11月08日21:39:53'
            }
        }

        self.Send_Transfers = [
            {
                'target': ('127.0.0.1', 8080),
                'bytes_crc32': 'crc32',
                'ctime': '2020年11月08日21:46:24',
                'atime': '2020年11月08日21:47:06',
                'losses': {1: {'mtime': '2020年11月08日21:56:45'}, 2: {}},
                'status': 0,  # 0准备 1等待握手 2握手成功 传输中 3 传输完成 4 超时
                'retry':0,
            }
        ]

        self.Recv_Transfers = [
            {
                'target': ('127.0.0.1', 8080),
                'bytes_crc32': 'crc32',
                'ctime': '2020年11月08日21:46:24',
                'atime': '2020年11月08日21:47:06',
                'losses': {},
                'status': 1  # 1 传输中 2 传输完成 3 超时
            }
        ]

        self.buffer_size = buffer_size



    def split_list(self, source_list, count):
        '''
        分割列表
        :param source_list: 欲被分割的列表
        :param count: 分割的数量
        :return: 分割后的列表
        '''
        c = int(math.ceil(len(source_list) / count))
        return_list = []
        for i in range(0, c):
            return_list.append(source_list[i * count:i * count + count])
        return return_list

    def hash_crc32(self, data):
        '''
        计算字节集的crc32值
        :param data: 字节集
        :return: 只取前8位十六进制CRC32值，如果不足则A补位
        '''
        crc32 = hex(zlib.crc32(data)).replace('0x', '')
        if len(crc32) < 8:
            crc32 = crc32.ljust(8, 'A')
        if len(crc32) > 8:
            crc32 = crc32[0:8]
        return crc32

    def service_start(self):
        self.UDP.bind((self.Host, self.Port))


def do_comp(key_name):
    print('do comp', key_name)
    data = b''
    keys = list(FilePieces[key_name]['data_list'].keys())
    keys.sort()
    print(keys)
    with open('c.txt', 'w') as f:
        for k_id in keys:
            f.write(FilePieces[key_name]['data_list'][k_id])
    print('COMP CRC32:', hex(zlib.crc32(data)))
    print('comp', key_name)
    del FilePieces[key_name]


def check_loss():
    while True:
        try:
            del_items = []
            for key_name in list(set(list(FilePieces.keys())) - set(done_list)):
                loss_count = len(FilePieces[key_name]['losses'].keys())
                if loss_count > 0:
                    if time.time() - FilePieces[key_name]['last_time'] > 0.1:
                        if time.time() - FilePieces[key_name]['last_time'] > 60:
                            del_items.append(key_name)
                            continue
                        send_lossid_list = []
                        for loss_id in FilePieces[key_name]['losses'].keys():
                            if FilePieces[key_name]['losses'][loss_id] == 0 or time.time() - \
                                    FilePieces[key_name]['losses'][loss_id] > 1:
                                send_lossid_list.append(loss_id)
                                FilePieces[key_name]['losses'][loss_id] = time.time()
                        if len(send_lossid_list) > 0:
                            loss_item_group = split_list(send_lossid_list, 10000)
                            for loss_list in loss_item_group:
                                send_data = b'2' + str(key_name).encode() + ','.join(map(str, loss_list)).encode()
                                udp_socket.sendto(send_data, FilePieces[key_name]['target_addr'])
                else:
                    if key_name not in done_list:
                        done_list.append(key_name)
                        do_comp(key_name)
            for item in del_items:
                del FilePieces[item]
        except:
            traceback.print_exc()
        finally:
            time.sleep(0.1)


def recv_msg():
    while True:
        try:
            recv_data, dest_ip = udp_socket.recvfrom(1024 * 4)
            threading.Thread(target=do, args=(recv_data, dest_ip,)).start()
        except:
            traceback.print_exc()


lock = threading.Lock()


def list_to_dict(obj):
    xdict = {}
    for o in obj:
        xdict[o] = 0
    return xdict


def do(recv_data, dest_ip):
    try:
        if len(recv_data) > 25:
            type = recv_data[:1]
            crc32 = recv_data[1:9].decode()
            packet_id = int(recv_data[9:17].decode())
            if type == b'1':
                count = int(recv_data[17:25].decode())
                data = recv_data[25:]
                if crc32 not in FilePieces:
                    FilePieces[crc32] = {
                        'count': count,
                        'data_list': {packet_id: data},
                        'last_time': time.time(),
                        'losses': list_to_dict(list(range(0, count))),
                        'target_addr': dest_ip
                    }
                    del FilePieces[crc32]['losses'][packet_id]
                else:
                    del FilePieces[crc32]['losses'][packet_id]
                    FilePieces[crc32]['data_list'][packet_id] = data
                    FilePieces[crc32]['last_time'] = time.time()
                    with lock:
                        if packet_id % 10 == 0:
                            print(
                                'Type:{}\tCRC32:{}\tPacket_ID:{}\tCount:{}\tRecvCount:{}\tLossCount:{}'.format(type,
                                                                                                               crc32,
                                                                                                               packet_id,
                                                                                                               count,
                                                                                                               len(
                                                                                                                   FilePieces[
                                                                                                                       crc32][
                                                                                                                       'data_list']),
                                                                                                               len(
                                                                                                                   FilePieces[
                                                                                                                       crc32][
                                                                                                                       'losses'].keys())))

            if type == b'4':
                crc32 = recv_data[1:9].decode()
                packet_id = int(recv_data[9:17].decode())
                data = recv_data[17:]
                if packet_id not in FilePieces[crc32]['data_list']:
                    FilePieces[crc32]['data_list'][packet_id] = data
                if packet_id in FilePieces[crc32]['losses'].keys():
                    del FilePieces[crc32]['losses'][packet_id]
    except:
        traceback.print_exc()


if __name__ == '__main__':
    print('init......')

    threading.Thread(target=check_loss).start()
    threading.Thread(target=recv_msg).start()
